package org.jtorrent.torrent;

import org.jtorrent.torrent.type.TorrentType;
import org.jtorrent.Constants;

import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Ryan Thomas
 * r.n.thomas@gmail.com
 *
 * @author ryan
 * @version $Revision$ $Author$ $Date$
 * @since Jun 20, 2009
 */
public class TorrentFileReader {
    private InputStream input;
    private File inputFile;
    private byte[] infoPacket;
    private long bytesRead;

    public TorrentFileReader(final File inputFile) throws FileNotFoundException {
        this.inputFile = inputFile;
        input = new FileInputStream(inputFile);
        bytesRead = 0;
    }

    public TorrentData read() throws InvalidTorrentFileException, IOException {
        int dictionaryStart = readByte();
        assertTrue(dictionaryStart, Constants.DICTIONARY_START);

        TorrentType<Map<String, TorrentType<?>>> data = readDictionary();
        input.close();

        return new TorrentData(data, infoPacket);
    }

    private int readByte() throws IOException {
        ++bytesRead;
        return input.read();
    }

    private int readBytes(byte[] bytes) throws IOException {
        int read = input.read(bytes);
        bytesRead += read;
        return read;
    }

    private TorrentType<Map<String, TorrentType<?>>> readDictionary() throws InvalidTorrentFileException, IOException {
        Map<String, TorrentType<?>> dict = new HashMap<String, TorrentType<?>>();
        int val = readByte();
        do {
            StringBuffer keyBuf = new StringBuffer();
            while(val != Constants.SEPARATOR) {
                keyBuf.append((char) val);
                val = readByte();
            }
            TorrentType<String> key = readString(Integer.valueOf(keyBuf.toString()));

            val = readByte();
            switch(val) {
                case Constants.INTEGER_START:
                    dict.put(key.getPayload(), readInt());
                    break;
                case Constants.LIST_START:
                    dict.put(key.getPayload(), readList());
                    break;
                case Constants.DICTIONARY_START:
                    if(Constants.KEY_INFO.equals(key.getPayload())) {
                        long start = bytesRead;
                        dict.put(key.getPayload(), readDictionary());
                        long end = bytesRead;
                        setInfoString(start, end);
                    }
                    else {
                        dict.put(key.getPayload(), readDictionary());
                    }
                    break;
                default: { // String types have no delimiter...
                    StringBuffer lenBuf = new StringBuffer();
                    while(val != Constants.SEPARATOR) {
                        lenBuf.append((char) val);
                        val = readByte();
                    }
                    dict.put(key.getPayload(), readString(Integer.valueOf(lenBuf.toString())));
                }
            }

            val = readByte();
        } while(val != Constants.DICTIONARY_END);
        
        return new TorrentType<Map<String, TorrentType<?>>>(dict);
    }

    private TorrentType<List> readList() throws InvalidTorrentFileException, IOException {
        List<TorrentType> list = new ArrayList<TorrentType>();
        int val = readByte();
        do {
            switch(val) {
                case Constants.INTEGER_START:
                    list.add(readInt());
                    break;
                case Constants.LIST_START:
                    list.add(readList());
                    break;
                case Constants.DICTIONARY_START:
                    list.add(readDictionary());
                    break;
                default: { // String types have no delimiter...
                    StringBuffer lenBuf = new StringBuffer();
                    while(val != Constants.SEPARATOR) {
                        lenBuf.append((char) val);
                        val = readByte();
                    }
                    list.add(readString(Integer.valueOf(lenBuf.toString())));
                }
            }

            val = readByte();
        } while(val != Constants.LIST_END);

        return new TorrentType<List>(list);
    }

    private TorrentType<String> readString(int length) throws InvalidTorrentFileException, IOException {
        byte[] str = new byte[length];
        assertTrue(readBytes(str), length);
        return new TorrentType<String>(new String(str));
    }

    private TorrentType<Integer> readInt() throws InvalidTorrentFileException, IOException {
        StringBuffer intBuf = new StringBuffer();
        char val = (char) readByte();
        while(val != Constants.INTEGER_END && val != Constants.SEPARATOR) {
            intBuf.append(val);
            val = (char) readByte();
        }
        return new TorrentType<Integer>(Integer.valueOf(intBuf.toString()));
    }

    private static void assertTrue(int a, int b) throws InvalidTorrentFileException {
        if(a != b) {
            throw new InvalidTorrentFileException(String.valueOf(a) + "(" + ((char) a) + ") does not equal: " + String.valueOf(b) + "(" + ((char) b) + ")");
        }
    }

    private void setInfoString(long start, long end) throws IOException {
        FileInputStream fis = new FileInputStream(inputFile);
        fis.skip(start - 1);
        infoPacket = new byte[(int) (end - start) + 1];
        fis.read(infoPacket);
        fis.close();
    }
}
